/*
 * Copyright (C) 2012-2013  Romain DUBOIS
 * 
 * This file is part of NeMoS - Network and Modular Software
 * 
 * NeMoS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * NeMoS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with NeMoS.  If not, see <http://www.gnu.org/licenses/>.
 */
package nemos.sample.network.swing;

import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;

import javax.swing.table.TableModel;

import nemos.remote.rsa.message.IConnection;
import nemos.remote.rsa.message.IServer;
import nemos.remote.rsa.message.MessageConfigurationConstants;
import nemos.remote.rsa.message.data.EFrameworkAction;
import nemos.remote.util.RemoteUtil;
import nemos.sample.network.IConnectedFramework;
import nemos.util.swing.ListTableModel;

import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.ReferencePolicy;
import org.apache.felix.scr.annotations.References;
import org.apache.felix.scr.annotations.Service;
import org.osgi.framework.Constants;
import org.osgi.service.event.Event;
import org.osgi.service.event.EventConstants;
import org.osgi.service.event.EventHandler;

/**
 * NetworkModel : The network model service.
 * 
 * @author Romain DUBOIS
 */
@Service({ EventHandler.class, NetworkModel.class })
@Property(name = EventConstants.EVENT_TOPIC, value = MessageConfigurationConstants.FRAMEWORK_EVENT_TOPIC_ROOT
        + "*")
@References({
        @Reference(name = "connection", referenceInterface = IConnection.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC),
        @Reference(name = "server", referenceInterface = IServer.class, cardinality = ReferenceCardinality.OPTIONAL_UNARY, policy = ReferencePolicy.DYNAMIC),
        @Reference(name = "framework", referenceInterface = IConnectedFramework.class, cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC) })
@Component
public class NetworkModel implements EventHandler {

    /** The connected frameworks table model */
    private final ListTableModel<String> mConnectedFrameworksModel;
    /** The connected framework models converter */
    private final ConnectedConverter mConnectedConverter;

    /** The current connection */
    private final AtomicReference<IConnection> mConnection;
    /** The selected framework */
    private String mSelected;

    /**
     * Constructor.
     */
    public NetworkModel() {
        super();
        mConnection = new AtomicReference<IConnection>();
        mConnectedConverter = new ConnectedConverter();
        mConnectedFrameworksModel = new ListTableModel<String>(mConnectedConverter);
    }

    //
    // OSGi component methods
    //

    /**
     * Bind a connection.
     * 
     * @param pConnection
     *            The connection to bind
     */
    public void bindConnection(final IConnection pConnection) {
        updateConnection(pConnection, false, true);
    }

    /**
     * Unbind a connection.
     * 
     * @param pConnection
     *            The connection to unbind
     */
    public void unbindConnection(final IConnection pConnection) {
        updateConnection(pConnection, false, false);
    }

    /**
     * Bind a server.
     * 
     * @param pServer
     *            The server to bind
     */
    public void bindServer(final IServer pServer) {
        updateConnection(pServer, true, true);
    }

    /**
     * Unbind a server.
     * 
     * @param pServer
     *            The server to unbind
     */
    public void unbindServer(final IServer pServer) {
        updateConnection(pServer, true, false);
    }

    /**
     * Update models with a connection.
     * 
     * @param pConnection
     *            The updated connection
     * @param pServer
     *            The server flag
     * @param pBind
     *            The bind flag
     */
    private void updateConnection(final IConnection pConnection, final boolean pServer,
            final boolean pBind) {

        final IConnection lCurrent = mConnection.get();
        final IConnection lNext;
        if (pBind) {
            lNext = pConnection;
        } else {
            lNext = null;
        }

        final boolean lUpdateFlag = lCurrent == null
                || (pServer && lCurrent.getId().equals(pConnection.getId()));

        // Update connection if first one or if it is the server version of the
        // current one
        if (pBind == lUpdateFlag) {

            // Check for asynchronous calls
            synchronized (this) {
                if (mConnection.compareAndSet(lCurrent, lNext)) {
                    // Now, update is possible

                    // Remove all data if old or new is null
                    if (lCurrent == null || lNext == null) {
                        mConnectedFrameworksModel.getList().clear();
                    }

                    // Update data if new is not null
                    if (lNext != null) {

                        // Add new ones
                        for (final String lFramework : lNext.getConnectedFrameworks()) {
                            if (!mConnectedFrameworksModel.getList().contains(lFramework)) {
                                mConnectedFrameworksModel.getList().add(lFramework);
                            }
                        }
                    }
                }
            }
        }
    }

    /**
     * Bind a framework.
     * 
     * @param pFramework
     *            The framework to bind
     * @param pProps
     *            The service properties
     */
    public void bindFramework(final IConnectedFramework pFramework, final Map<String, Object> pProps) {
        updateFramework(pFramework, pProps, true);
    }

    /**
     * Unbind a framework.
     * 
     * @param pFramework
     *            The framework to unbind
     * @param pProps
     *            The service properties
     */
    public void unbindFramework(final IConnectedFramework pFramework,
            final Map<String, Object> pProps) {
        updateFramework(pFramework, pProps, false);
    }

    /**
     * Update the connected frameworks table model.
     * 
     * @param pFramework
     *            The framework to (un)bind
     * @param pProps
     *            The service properties
     * @param pBind
     *            The bind flag
     */
    private void updateFramework(final IConnectedFramework pFramework,
            final Map<String, Object> pProps, final boolean pBind) {

        // Get source framework UUID
        String lUuid = (String) pProps
                .get(org.osgi.service.remoteserviceadmin.RemoteConstants.ENDPOINT_FRAMEWORK_UUID);
        // Local case
        if (lUuid == null) {
            lUuid = RemoteUtil.getFrameworkUuid();
        }

        // Update description
        if (pBind) {
            mConnectedConverter.setDescription(lUuid, pFramework.getDescription());
        } else {
            mConnectedConverter.removeDescription(lUuid);
        }

        // Update list
        synchronized (this) {
            final int lIndex = mConnectedFrameworksModel.getList().indexOf(lUuid);

            // Update framework (add/remove description)
            if (lIndex != -1) {
                // Force model notification
                mConnectedFrameworksModel.getList().set(lIndex, lUuid);
            }
        }
    }

    //
    // Event method
    //

    /**
     * {@inheritDoc}
     * 
     * @see org.osgi.service.event.EventHandler#handleEvent(org.osgi.service.event.Event)
     */
    @Override
    public void handleEvent(final Event pEvent) {
        // Collect event type
        final String lTopic = pEvent.getTopic();
        final String lActionName = lTopic.substring(lTopic.lastIndexOf('/') + 1);

        // And its data
        final EFrameworkAction lAction = EFrameworkAction.valueOf(lActionName);
        final String lFramework = (String) pEvent.getProperty(Constants.FRAMEWORK_UUID);

        // Event is valid
        if (lAction != null && lFramework != null) {
            final boolean lAdded = lAction == EFrameworkAction.JOIN;

            synchronized (this) {
                final boolean lExist = mConnectedFrameworksModel.getList().contains(lFramework);

                // Add if required
                if (!lExist && lAdded) {
                    mConnectedFrameworksModel.getList().add(lFramework);
                }
                // Remove if needed
                else if (!lAdded) {
                    mConnectedFrameworksModel.getList().remove(lFramework);
                }
            }
        }
    }

    //
    // Model methods
    //

    /**
     * Get the table model containing the frameworks connected to the
     * connection.
     * 
     * @return The connected frameworks model
     */
    public TableModel getConnectedFrameworks() {
        return mConnectedFrameworksModel;
    }

    /**
     * Change the selected framework.
     * 
     * @param pSelectedRow
     *            The selected row
     */
    public void setSelectedConnection(final int pSelectedRow) {
        // Update
        synchronized (this) {
            if (mConnectedFrameworksModel.getList().size() > pSelectedRow && pSelectedRow >= 0) {
                mSelected = mConnectedFrameworksModel.getList().get(pSelectedRow);
            } else {
                mSelected = null;
            }
        }
    }

    /**
     * Get the selected framework.
     * 
     * @return The selected framework
     */
    public String getSelectedFramework() {
        return mSelected;
    }
}
